/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.client.editor.controller;

import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ResourceBundle;

import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JEditorPane;
import javax.swing.KeyStroke;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.HTMLEditorKit.HTMLTextAction;
import javax.swing.tree.DefaultMutableTreeNode;

import offset.nodes.client.editor.Editor;
import offset.nodes.client.editor.model.DocumentContext;
import offset.nodes.client.editor.model.EditorDocument;
import offset.nodes.client.editor.model.ListModel;
import offset.nodes.client.editor.model.ParagraphModel;
import offset.nodes.client.editor.model.Range;
import offset.nodes.client.editor.model.StructureTree;
import offset.nodes.client.editor.model.TableModel;

/**
 *
 * @author
 * Walter
 * Lütgenau
 */
public abstract class EditorActions {

    protected static Action defaultInsertTabAction;
    protected static Action defaultInsertBreakAction;
    protected static Action defaultDeletePreviousAction;
    protected static Action defaultCaretUpAction;
    protected static Action defaultCaretDownAction;
    protected static Action defaultCaretLeftAction;
    protected static Action defaultCaretRightAction;

    public static abstract class AbstractEditorAction extends HTMLTextAction {

        Editor editor;
        static protected ResourceBundle localizer = ResourceBundle.getBundle("offset/nodes/client/editor/view/resources/EditorBundle");

        /**
         * Creates a
         * new
         * instance
         * of
         * DocumentAction
         *
         * @param
         * name
         * @param
         * editor
         */
        public AbstractEditorAction(String name, Editor editor) {
            super(name);
            this.editor = editor;
            putValue(NAME, name);
            putValue(SHORT_DESCRIPTION, name);
        }

        public static ResourceBundle getBundle() {
            return localizer;
        }

        public void setIcon(ImageIcon icon) {
            putValue(SMALL_ICON, icon);
        }

        public void setName(String name) {
            putValue(NAME, name);
        }

        public void setAccelerator(KeyStroke keystroke) {
            putValue(ACCELERATOR_KEY, keystroke);
        }

        /**
         * Accessor
         * of
         * editor
         *
         * @return
         */
        public Editor getEditor() {
            return editor;
        }

        /**
         * Better
         * name
         * in
         * our
         * context.
         *
         * @param
         * ae
         * @return
         */
        public JEditorPane getEditorPane(ActionEvent ae) {
            return getEditor(ae);
        }

        public EditorDocument getDocument(ActionEvent ae) {
            JEditorPane editor = getEditorPane(ae);
            return (EditorDocument) editor.getDocument();
        }

        public HTMLEditorKit getEditorKit(ActionEvent ae) {
            JEditorPane editor = getEditorPane(ae);
            return (HTMLEditorKit) editor.getEditorKit();
        }

        public DocumentContext getDocumentContext(ActionEvent ae) {
            return new DocumentContext(getEditorPane(ae));
        }

        public DocumentContext getDocumentContext() {
            return new DocumentContext(getEditor().getBrowserPane());
        }

        public void refreshCaret(ActionEvent ae) {
            JEditorPane editor = getEditorPane(ae);

            editor.setCaretPosition(editor.getCaretPosition());
            editor.setSelectionStart(editor.getSelectionStart());
            editor.setSelectionEnd(editor.getSelectionEnd());
            editor.grabFocus();
        }

        public void refreshCaret(Editor ed, Range range) {
            JEditorPane editor = ed.getBrowserPane();

            editor.setCaretPosition(range.getStartOffset());
            editor.setSelectionStart(range.getStartOffset());
            editor.setSelectionEnd(range.getEndOffset());
            editor.grabFocus();
        }

        public void scrollToDocumentPosition(JEditorPane pane, int pos) throws BadLocationException {
            Rectangle r = pane.modelToView(pos);
            if (r != null) {
                if (r.width == 0)
                    r.width = 1;
                // the view is visible, scroll it to the 
                // center of the current visible area.
                Rectangle vis = pane.getVisibleRect();
                if (!vis.contains(r)) {
                    r.height = vis.height;
                    pane.scrollRectToVisible(r);
                }
            }
        }

        public void refreshCaret(ActionEvent ae, Range range) {
            JEditorPane editor = getEditorPane(ae);

            editor.setCaretPosition(range.getStartOffset());
            editor.setSelectionStart(range.getStartOffset());
            editor.setSelectionEnd(range.getEndOffset());
            editor.grabFocus();
        }

        public void actionPerformed(ActionEvent e) {
            try {
                JEditorPane pane = getEditorPane(e);
                getEditor().getUndoManager().startAction(pane);
                action(e);
                getEditor().getUndoManager().endAction(pane);
                grabFocus(pane);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        
        public void grabFocus(JEditorPane pane) {
            if (pane != null)
                pane.grabFocus();
        }

        protected URL getContext() throws MalformedURLException {
            String document = (String) editor.getProperties().get(Editor.PROP_DOCUMENT_URL);
            String documentPath = (String) editor.getProperties().get(Editor.PROP_DOCUMENT_PATH);


            URL context = new URL(document);
            String path = null;
            if (documentPath != null && documentPath.length() > 0)
                path = context.getPath().substring(0, context.getPath().indexOf(documentPath));
            else
                path = context.getPath();
            context = new URL(context.getProtocol(), context.getHost(), context.getPort(), path);

            return context;
        }

        public abstract void action(ActionEvent e) throws Exception;
    }

    public static class InsertBreakAction extends AbstractEditorAction {

        ListActions.ListInsertBreakAction listInsertBreakAction;
        Action defaultAction = null;

        public InsertBreakAction(Editor editor) {
            super("editor-insert-break", editor);
            listInsertBreakAction = new ListActions.ListInsertBreakAction(editor);
            defaultAction = defaultInsertBreakAction;
        }

        protected boolean isEmptyDataElement(int pos) {
            DefaultMutableTreeNode selected = (DefaultMutableTreeNode) getEditor().getStructureTree().getSelectionPath().getLastPathComponent();
            Element e = (Element) selected.getUserObject();
            if (e instanceof StructureTree.XMLElement)
                return true;

            return false;
        }

        public void action(ActionEvent e) throws Exception {
            ListModel listModel = new ListModel(getDocumentContext(e));
            ParagraphModel paragraphModel = new ParagraphModel(getDocumentContext(e));

            int pos = getEditorPane(e).getCaretPosition();

            if (paragraphModel.isEndOfHeading(pos)) {
                paragraphModel.addParagraphAfterHeading(pos);
                getEditorPane(e).setCaretPosition(pos + 1);
                return;
            } else if (listModel.isListItem(pos)) {
                listInsertBreakAction.action(e);
                return;
            }

            if (defaultAction != null)
                defaultAction.actionPerformed(e);
        }
    }

    public static class TabAction extends AbstractEditorAction {

        static ListActions.ListTabAction listTabAction = null;
        TableActions.TableTabAction tableTabAction;
        static Action defaultAction = null;

        public TabAction(Editor editor) {
            super(localizer.getString("tab"), editor);
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_incrementindent.png")));
            defaultAction = defaultInsertTabAction;
            listTabAction = new ListActions.ListTabAction(defaultAction, editor);
            tableTabAction = new TableActions.TableTabAction(editor);
        }

        public void action(ActionEvent e) throws Exception {
            ListModel listModel = new ListModel(getDocumentContext(e));
            TableModel tableModel = new TableModel(getDocumentContext(e));

            int pos = getEditorPane(e).getCaretPosition();

            if (listModel.isListItem(pos)) {
                listTabAction.action(e);
                return;
            } else if (tableModel.isTable(pos)) {
                tableTabAction.action(e);
                return;
            }

            if (defaultAction != null)
                defaultAction.actionPerformed(e);
        }
    }

    public static class BackTabAction extends AbstractEditorAction {

        ListActions.ListBackTabAction listBackTabAction;
        TableActions.TableBackTabAction tableBackTabAction;

        public BackTabAction(Editor editor) {
            super(localizer.getString("backTab"), editor);
            setIcon(new javax.swing.ImageIcon(getClass().getResource("/offset/nodes/client/editor/view/resources/sc_decrementindent.png")));
            listBackTabAction = new ListActions.ListBackTabAction(editor);
            tableBackTabAction = new TableActions.TableBackTabAction(editor);
        }

        public void action(ActionEvent e) throws Exception {
            ListModel listModel = new ListModel(getDocumentContext(e));
            TableModel tableModel = new TableModel(getDocumentContext(e));

            int pos = getEditorPane(e).getCaretPosition();

            if (listModel.isListItem(pos)) {
                listBackTabAction.action(e);
                return;
            } else if (tableModel.isTable(pos)) {
                tableBackTabAction.action(e);
                return;
            }
        }
    }

    public static class CaretLeftAction extends AbstractEditorAction {

        public CaretLeftAction(Editor editor) {
            super("", editor);
        }

        public void action(ActionEvent e) throws Exception {
            getDefaultCaretLeftAction().actionPerformed(e);
            getEditor().fireUserCaretEvent();
        }
    }

    public static class CaretRightAction extends AbstractEditorAction {

        public CaretRightAction(Editor editor) {
            super("", editor);
        }

        public void action(ActionEvent e) throws Exception {
            getDefaultCaretRightAction().actionPerformed(e);
            getEditor().fireUserCaretEvent();
        }
    }

    public static class CaretUpAction extends AbstractEditorAction {

        static TableActions.TableCaretUpAction tableCaretUpAction = null;
        static TableActions.TableCaretEnterAction tableCaretEnterAction = null;

        public CaretUpAction(Editor editor) {
            super("", editor);
            tableCaretUpAction = new TableActions.TableCaretUpAction(getDefaultCaretUpAction(), editor);
            tableCaretEnterAction = new TableActions.TableCaretEnterAction(getDefaultCaretUpAction(), editor);
        }

        public void action(ActionEvent e) throws Exception {
            TableModel tableModel = new TableModel(getDocumentContext(e));

            int pos = getEditorPane(e).getCaretPosition();

            if (tableModel.isTable(pos)) {
                tableCaretUpAction.action(e);
                return;
            }

            getDefaultCaretUpAction().actionPerformed(e);

            if (tableModel.isTable(getEditorPane(e).getCaretPosition()))
                tableCaretEnterAction.action(e);

            getEditor().fireUserCaretEvent();
        }
    }

    public static class CaretDownAction extends AbstractEditorAction {

        static TableActions.TableCaretDownAction tableCaretDownAction = null;
        static TableActions.TableCaretEnterAction tableCaretEnterAction = null;

        public CaretDownAction(Editor editor) {
            super("", editor);
            tableCaretDownAction = new TableActions.TableCaretDownAction(getDefaultCaretDownAction(), editor);
            tableCaretEnterAction = new TableActions.TableCaretEnterAction(getDefaultCaretDownAction(), editor);
        }

        public void action(ActionEvent e) throws Exception {
            TableModel tableModel = new TableModel(getDocumentContext(e));

            int pos = getEditorPane(e).getCaretPosition();

            if (tableModel.isTable(pos)) {
                tableCaretDownAction.action(e);
                return;
            }

            getDefaultCaretDownAction().actionPerformed(e);

            if (tableModel.isTable(getEditorPane(e).getCaretPosition()))
                tableCaretEnterAction.action(e);

            getEditor().fireUserCaretEvent();
        }
    }

    public static class DeletePrevCharAction extends AbstractEditorAction {

        ListActions.ListDeletePrevCharAction listDeletePrevCharAction = null;
        TableActions.TableDeletePrevCharAction tableDeletePrevCharAction = null;
        Action defaultAction = null;

        public DeletePrevCharAction(Editor editor) {
            super("editor-delete-previous", editor);
            defaultAction = defaultDeletePreviousAction;
            listDeletePrevCharAction = new ListActions.ListDeletePrevCharAction(defaultAction, editor);
            tableDeletePrevCharAction = new TableActions.TableDeletePrevCharAction(defaultAction, editor);
        }

        public void action(ActionEvent e) throws Exception {
            ListModel listModel = new ListModel(getDocumentContext(e));
            TableModel tableModel = new TableModel(getDocumentContext(e));
            ParagraphModel paragraphModel = new ParagraphModel(getDocumentContext(e));

            int pos = getEditorPane(e).getCaretPosition();
            if (paragraphModel.isStartOfParagraph(getEditorPane(e).getCaretPosition()) && listModel.isList(pos - 1)) {
                listModel.mergeParagraphWithList(pos);
                getEditorPane(e).setCaretPosition(pos);
                return;
            }

            if (listModel.isListItem(pos)) {
                listDeletePrevCharAction.action(e);
                return;
            }

            if (tableModel.isTable(pos)) {
                tableDeletePrevCharAction.action(e);
                return;
            }

            if (defaultAction != null)
                defaultAction.actionPerformed(e);
        }
    }

    public static class NullAction extends AbstractEditorAction {

        public NullAction(Editor editor) {
            super("null-action", editor);
        }

        public void action(ActionEvent e) {
        }
    }

    public static void setDefaultInsertTabAction(Action _defaultInsertTabAction) {
        defaultInsertTabAction = _defaultInsertTabAction;
    }

    public static void setDefaultInsertBreakAction(Action _defaultInsertBreakAction) {
        defaultInsertBreakAction = _defaultInsertBreakAction;
    }

    public static void setDefaultDeletePreviousAction(Action _defaultDeletePreviousAction) {
        defaultDeletePreviousAction = _defaultDeletePreviousAction;
    }

    public static Action getDefaultCaretDownAction() {
        return defaultCaretDownAction;
    }

    public static void setDefaultCaretDownAction(Action defaultCaretDownAction) {
        EditorActions.defaultCaretDownAction = defaultCaretDownAction;
    }

    public static Action getDefaultCaretUpAction() {
        return defaultCaretUpAction;
    }

    public static void setDefaultCaretUpAction(Action defaultCaretUpAction) {
        EditorActions.defaultCaretUpAction = defaultCaretUpAction;
    }

    public static Action getDefaultCaretLeftAction() {
        return defaultCaretLeftAction;
    }

    public static void setDefaultCaretLeftAction(Action defaultCaretLeftAction) {
        EditorActions.defaultCaretLeftAction = defaultCaretLeftAction;
    }

    public static Action getDefaultCaretRightAction() {
        return defaultCaretRightAction;
    }

    public static void setDefaultCaretRightAction(Action defaultCaretRightAction) {
        EditorActions.defaultCaretRightAction = defaultCaretRightAction;
    }
}
